#include "czDisplay.h"

#include <memory.h>
#include <assert.h>

WindowX11::TKeyMap WindowX11::normalKeys_;
WindowX11::TKeyMap WindowX11::functionKeys_;
WindowX11::TKeyFlags WindowX11::keyIsPressed_;
WindowX11::TKeyFlags WindowX11::keyIsReleased_;
bool WindowX11::keyMapsInitialized_ = WindowX11::initializeKeyMaps();

WindowX11::~WindowX11()
{
  close();
}

bool WindowX11::open(Context* context, InputListener* listener, const char title[], int width, int height )
{
  x11Window	= 0;
  x11Display	= 0;
  x11Screen	= 0;
  x11Visual	= 0;
  x11Colormap	= 0;

  sRootWindow = 0;
  ui32Mask = 0;
  i32Depth = 0;
  isShuttingDown_ = false;

  magical_strcpy(_title, title);

  this->context = context;
  this->_width = width;
  this->_height = height;

  this->mode = Windowed;
  this->_open = true;

  _listener = listener;

  x11Display = ::XOpenDisplay(0);
  if (!x11Display)
  {
    close();
    return false;
  }

  x11Screen = XDefaultScreen(x11Display);
  x11Visual = new XVisualInfo;
  sRootWindow = RootWindow(x11Display, x11Screen);
  i32Depth = DefaultDepth(x11Display, x11Screen);
  XMatchVisualInfo(x11Display, x11Screen, i32Depth, TrueColor, x11Visual);
  if (!x11Visual)
  {
    close();
    return false;
  }


  // let's create a window

  //const Window root = DefaultRootWindow(x11Display);

  const int screenWidth = DisplayWidth(x11Display, x11Screen);
  const int screenHeight = DisplayHeight(x11Display, x11Screen);
  const int left = (screenWidth - width) / 2;
  const int top = (screenHeight - height) / 2;

  x11Colormap = XCreateColormap( x11Display, sRootWindow, x11Visual->visual, AllocNone );
  sWA.colormap = x11Colormap;
  sWA.border_pixel = sWA.background_pixel = BlackPixel(x11Display, x11Screen);
  sWA.backing_store = NotUseful;
  sWA.event_mask = StructureNotifyMask | ExposureMask | ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask;

  ui32Mask = CWBackPixel | CWBorderPixel | CWEventMask | CWColormap;
  x11Window = ::XCreateWindow(x11Display, RootWindow(x11Display, x11Screen),
    left, top, width, height, 0,
    i32Depth, InputOutput, CopyFromParent,
    ui32Mask, &sWA);


  ::XStoreName(x11Display, x11Window, title); // set title string

  wmProtocols_ = XInternAtom(x11Display, "WM_PROTOCOLS", True);
  wmDeleteWindow_ = XInternAtom(x11Display, "WM_DELETE_WINDOW", True);
  if (wmProtocols_ == 0 || wmDeleteWindow_ == 0)
  {
    close();
    return false;
  }
  if (::XSetWMProtocols(x11Display, x11Window, &wmDeleteWindow_, 1) == 0)
  {
    close();
    return false;
  }

  ::XSizeHints sizeHints;
  sizeHints.flags = PPosition | PMinSize | PMaxSize;
  sizeHints.x = sizeHints.y = 0;
  sizeHints.min_width = sizeHints.max_width = width;
  sizeHints.min_height = sizeHints.max_height = height;
  ::XSetNormalHints(x11Display, x11Window, &sizeHints);
  ::XClearWindow(x11Display, x11Window);
  ::XSelectInput(x11Display, x11Window, eventMask_);

  // we have a winner!
  ::XMapRaised(x11Display, x11Window);
  ::XFlush(x11Display);

  if(context)
  {
    if( !context->init(this) )
      return false;
  }

  if ( _listener )
    _listener->onOpen(*this);

  return true;
}

void WindowX11::close()
{
  if ( _listener )
    _listener->onClose(*this);

  if(x11Visual)
  {
    delete x11Visual;
    x11Visual = 0;
  }

  if (context)
  {
    context->shutdown();
    context = 0;
  }

  _close();
}

// show the window (it is initially hidden)

void WindowX11::show()
{
}

// hide the window

void WindowX11::hide()
{
}

// check if window is visible?

bool WindowX11::visible() const
{
  return true;
}

// put window in fullscreen mode

void WindowX11::fullscreen(int width, int height)
{
}

// put window in windowed mode

void WindowX11::windowed( int width, int height )
{
}

// center the window on the desktop
// note: has no effect if the window is minimized or maximized

void WindowX11::center()
{
}

// zoom window

void WindowX11::zoom( float scale )
{
}

bool WindowX11::isRun() const
{
  return !isShuttingDown_;
}

void WindowX11::update()
{
  if (context)
    context->update();

  pumpEvents();
}

// get the window handle.
// null if the window failed to initialize.

WindowHandle WindowX11::handle() const
{
  return x11Window;
}

int WindowX11::getWidth() const
{
  return _width;
}
int WindowX11::getHeight() const
{
  return _height;
}
bool WindowX11::getActive() const
{
  return true;
}

Mode WindowX11::getOutputMode() const
{
  return Windowed;
}

// title management

void WindowX11::title(const char title[])
{
  magical_strcpy(_title, title);

  if (x11Display && x11Window)
    ::XStoreName(x11Display, x11Window, title);
}

// listener management

InputListener * WindowX11::listener() const
{
  return _listener;
}

void WindowX11::_close()
{
  if (x11Display && x11Window)
  {
    XDestroyWindow(x11Display, x11Window);
    x11Window = 0;
  }

  if (x11Display)
  {
    XCloseDisplay(x11Display);
    x11Display = 0;
  }

  if( x11Visual )
  {
    delete x11Visual;
    x11Visual = 0;
  }
  _title[0] = 0;
  _width = 0;
  _height = 0;
  _open = false;
}

void WindowX11::pumpEvents()
{
  ::XEvent event;
  while (true)
  {
    if (::XCheckWindowEvent(x11Display, x11Window, -1, &event))
    {
      handleEvent(event);
    }
    else if (::XCheckTypedEvent(x11Display, ClientMessage, &event))
    {
      handleEvent(event);
    }
    else
    {
      break;
    }
  }

  // send key press and up events

  for (int i = 0; i < keyMapSize_; ++i)
  {
    if (keyIsReleased_[i] && keyIsPressed_[i])
    {
      if (listener()) listener()->onKeyUp(wrapper() ? *wrapper() : *(WindowInterface*)this,static_cast<Key::Code>(i));
      keyIsPressed_[i] = false;
      keyIsReleased_[i] = false;
    }
    else if (keyIsPressed_[i])
    {
      if (listener()) listener()->onKeyPressed(wrapper() ? *wrapper() : *(WindowInterface*)this,static_cast<Key::Code>(i));
    }
  }
}

void WindowX11::handleEvent(const ::XEvent& event)
{
  switch (event.type)
  {
  case KeyPress:
  case KeyRelease:
    {
      const KeySym keySym = ::XKeycodeToKeysym(x11Display, event.xkey.keycode, 0);
      const int hiSym = (keySym & 0xff00) >> 8;
      const int loSym = keySym & 0xff;

      Key key = Key::Undefined;
      switch (hiSym)
      {
      case 0x00:
        key = normalKeys_[loSym];
        break;
      case 0xff:
        key = functionKeys_[loSym];
        break;
      }

      if (event.type == KeyPress)
      {
        if (!keyIsPressed_[key])
        {
          bool defaultKeyHandlers = true;

          if (listener())
          {
            listener()->onKeyDown(wrapper() ? *wrapper() : *(WindowInterface*)this,key);
            defaultKeyHandlers = listener()->defaultKeyHandlers();
          }

          if (defaultKeyHandlers && key == Key::Escape)
          {
            isShuttingDown_ = true;
          }

        }
        keyIsPressed_[key] = true;
        keyIsReleased_[key] = false;
      }
      else
      {
        keyIsReleased_[key] = true;
      }
      break;
    }

  case ButtonPress:
  case ButtonRelease:
    {
      Mouse mouse;
      mouse.x = static_cast<float>(event.xbutton.x);
      mouse.y = static_cast<float>(event.xbutton.y);
      mouse.buttons.left = event.xbutton.button == Button1;
      mouse.buttons.middle = event.xbutton.button == Button2;
      mouse.buttons.right = event.xbutton.button == Button3;
      if (event.type == ButtonPress)
      {
        if (listener()) listener()->onMouseButtonDown(wrapper() ? *wrapper() : *(WindowInterface*)this,mouse);
      }
      else
      {
        if (listener()) listener()->onMouseButtonUp(wrapper() ? *wrapper() : *(WindowInterface*)this,mouse);
      }
      break;
    }
  case MotionNotify:
    {
      Mouse mouse;
      mouse.x = static_cast<float>(event.xmotion.x);
      mouse.y = static_cast<float>(event.xmotion.y);
      mouse.buttons.left = event.xmotion.state & Button1Mask;
      mouse.buttons.middle = event.xmotion.state & Button2Mask;
      mouse.buttons.right = event.xmotion.state & Button3Mask;
      if (listener()) listener()->onMouseMove(wrapper() ? *wrapper() : *(WindowInterface*)this,mouse);
      break;
    }
  case ClientMessage:
    {
      if (event.xclient.message_type == wmProtocols_ &&
        event.xclient.format == 32 &&
        event.xclient.data.l[0] == (long) wmDeleteWindow_)
      {
        if (listener())
        {
          if (listener()->onClose(wrapper() ? *wrapper() : *(WindowInterface*)this))
            isShuttingDown_ = true;
        }
        else
        {
          isShuttingDown_ = true;
        }
      }
      break;
    }
  }
}


bool WindowX11::initializeKeyMaps()
{
  for (int i = 0; i < keyMapSize_; ++i)
  {
    normalKeys_[i] = Key::Undefined;
    functionKeys_[i] = Key::Undefined;
    keyIsPressed_[i] = false;
    keyIsReleased_[i] = false;
  }

  normalKeys_[XK_space] = Key::Space;
  normalKeys_[XK_comma] = Key::Comma;
  normalKeys_[XK_period] = Key::Period;
  normalKeys_[XK_slash] = Key::Slash;
  normalKeys_[XK_0] = Key::Zero;
  normalKeys_[XK_1] = Key::One;
  normalKeys_[XK_2] = Key::Two;
  normalKeys_[XK_3] = Key::Three;
  normalKeys_[XK_4] = Key::Four;
  normalKeys_[XK_5] = Key::Five;
  normalKeys_[XK_6] = Key::Six;
  normalKeys_[XK_7] = Key::Seven;
  normalKeys_[XK_8] = Key::Eight;
  normalKeys_[XK_9] = Key::Nine;
  normalKeys_[XK_semicolon] = Key::SemiColon;
  normalKeys_[XK_equal] = Key::Equals;
  normalKeys_[XK_a] = Key::A;
  normalKeys_[XK_b] = Key::B;
  normalKeys_[XK_c] = Key::C;
  normalKeys_[XK_d] = Key::D;
  normalKeys_[XK_e] = Key::E;
  normalKeys_[XK_f] = Key::F;
  normalKeys_[XK_g] = Key::G;
  normalKeys_[XK_h] = Key::H;
  normalKeys_[XK_i] = Key::I;
  normalKeys_[XK_j] = Key::J;
  normalKeys_[XK_k] = Key::K;
  normalKeys_[XK_l] = Key::L;
  normalKeys_[XK_m] = Key::M;
  normalKeys_[XK_n] = Key::N;
  normalKeys_[XK_o] = Key::O;
  normalKeys_[XK_p] = Key::P;
  normalKeys_[XK_q] = Key::Q;
  normalKeys_[XK_r] = Key::R;
  normalKeys_[XK_s] = Key::S;
  normalKeys_[XK_t] = Key::T;
  normalKeys_[XK_u] = Key::U;
  normalKeys_[XK_v] = Key::V;
  normalKeys_[XK_w] = Key::W;
  normalKeys_[XK_x] = Key::X;
  normalKeys_[XK_y] = Key::Y;
  normalKeys_[XK_z] = Key::Z;
  normalKeys_[XK_bracketleft] = Key::OpenBracket;
  normalKeys_[XK_backslash] = Key::BackSlash;
  normalKeys_[XK_bracketright] = Key::CloseBracket;

  functionKeys_[0xff & XK_BackSpace] = Key::BackSpace;
  functionKeys_[0xff & XK_Tab] = Key::Tab;
  functionKeys_[0xff & XK_Linefeed] = Key::Undefined;
  functionKeys_[0xff & XK_Clear] = Key::Clear;
  functionKeys_[0xff & XK_Return] = Key::Enter;
  functionKeys_[0xff & XK_Pause] = Key::Pause;
  functionKeys_[0xff & XK_Scroll_Lock] = Key::ScrollLock;
  functionKeys_[0xff & XK_Sys_Req] = Key::PrintScreen;
  functionKeys_[0xff & XK_Escape] = Key::Escape;
  functionKeys_[0xff & XK_Delete] = Key::Delete;
  functionKeys_[0xff & XK_Kanji] = Key::Kanji;
  functionKeys_[0xff & XK_Kana_Shift] = Key::Kana;
  functionKeys_[0xff & XK_Home] = Key::Home;
  functionKeys_[0xff & XK_Left] = Key::Left;
  functionKeys_[0xff & XK_Up] = Key::Up;
  functionKeys_[0xff & XK_Right] = Key::Right;
  functionKeys_[0xff & XK_Down] = Key::Down;
  functionKeys_[0xff & XK_Prior] = Key::Undefined;
  functionKeys_[0xff & XK_Page_Up] = Key::PageUp;
  functionKeys_[0xff & XK_Next] = Key::Undefined;
  functionKeys_[0xff & XK_Page_Down] = Key::PageDown;
  functionKeys_[0xff & XK_End] = Key::End;
  functionKeys_[0xff & XK_Begin] = Key::Undefined;
  functionKeys_[0xff & XK_Select] = Key::Undefined;
  functionKeys_[0xff & XK_Print] = Key::Undefined;
  functionKeys_[0xff & XK_Execute] = Key::Undefined;
  functionKeys_[0xff & XK_Insert] = Key::Insert;
  functionKeys_[0xff & XK_Undo] = Key::Undefined;
  functionKeys_[0xff & XK_Redo] = Key::Undefined;
  functionKeys_[0xff & XK_Menu] = Key::Undefined;
  functionKeys_[0xff & XK_Find] = Key::Undefined;
  functionKeys_[0xff & XK_Cancel] = Key::Cancel;
  functionKeys_[0xff & XK_Help] = Key::Help;
  functionKeys_[0xff & XK_Break] = Key::Undefined;
  functionKeys_[0xff & XK_Mode_switch] = Key::ModeChange;
  functionKeys_[0xff & XK_Num_Lock] = Key::NumLock;
  functionKeys_[0xff & XK_KP_Space] = Key::Space;
  functionKeys_[0xff & XK_KP_Tab] = Key::Tab;
  functionKeys_[0xff & XK_KP_Enter] = Key::Enter;
  functionKeys_[0xff & XK_KP_F1] = Key::F1;
  functionKeys_[0xff & XK_KP_F2] = Key::F2;
  functionKeys_[0xff & XK_KP_F3] = Key::F3;
  functionKeys_[0xff & XK_KP_F4] = Key::F4;
  functionKeys_[0xff & XK_KP_Home] = Key::Home;
  functionKeys_[0xff & XK_KP_Left] = Key::Left;
  functionKeys_[0xff & XK_KP_Right] = Key::Right;
  functionKeys_[0xff & XK_KP_Down] = Key::Down;
  functionKeys_[0xff & XK_KP_Prior] = Key::Undefined;
  functionKeys_[0xff & XK_KP_Page_Up] = Key::PageUp;
  functionKeys_[0xff & XK_KP_Next] = Key::Undefined;
  functionKeys_[0xff & XK_KP_Page_Down] = Key::PageDown;
  functionKeys_[0xff & XK_KP_End] = Key::End;
  functionKeys_[0xff & XK_KP_Begin] = Key::Undefined;
  functionKeys_[0xff & XK_KP_Insert] = Key::Insert;
  functionKeys_[0xff & XK_KP_Delete] = Key::Delete;
  functionKeys_[0xff & XK_KP_Equal] = Key::Equals;
  functionKeys_[0xff & XK_KP_Multiply] = Key::Multiply;
  functionKeys_[0xff & XK_KP_Add] = Key::Add;
  functionKeys_[0xff & XK_KP_Separator] = Key::Separator;
  functionKeys_[0xff & XK_KP_Subtract] = Key::Subtract;
  functionKeys_[0xff & XK_KP_Decimal] = Key::Decimal;
  functionKeys_[0xff & XK_KP_Divide] = Key::Divide;
  functionKeys_[0xff & XK_KP_0] = Key::NumPad0;
  functionKeys_[0xff & XK_KP_1] = Key::NumPad1;
  functionKeys_[0xff & XK_KP_2] = Key::NumPad2;
  functionKeys_[0xff & XK_KP_3] = Key::NumPad3;
  functionKeys_[0xff & XK_KP_4] = Key::NumPad4;
  functionKeys_[0xff & XK_KP_5] = Key::NumPad5;
  functionKeys_[0xff & XK_KP_6] = Key::NumPad6;
  functionKeys_[0xff & XK_KP_7] = Key::NumPad7;
  functionKeys_[0xff & XK_KP_8] = Key::NumPad8;
  functionKeys_[0xff & XK_KP_9] = Key::NumPad9;
  functionKeys_[0xff & XK_F1] = Key::F1;
  functionKeys_[0xff & XK_F2] = Key::F2;
  functionKeys_[0xff & XK_F3] = Key::F3;
  functionKeys_[0xff & XK_F4] = Key::F4;
  functionKeys_[0xff & XK_F5] = Key::F5;
  functionKeys_[0xff & XK_F6] = Key::F6;
  functionKeys_[0xff & XK_F7] = Key::F7;
  functionKeys_[0xff & XK_F8] = Key::F8;
  functionKeys_[0xff & XK_F9] = Key::F9;
  functionKeys_[0xff & XK_F10] = Key::F10;
  functionKeys_[0xff & XK_F11] = Key::F11;
  functionKeys_[0xff & XK_F12] = Key::F12;
  functionKeys_[0xff & XK_Shift_L] = Key::Shift;
  functionKeys_[0xff & XK_Shift_R] = Key::Shift;
  functionKeys_[0xff & XK_Control_L] = Key::Control;
  functionKeys_[0xff & XK_Control_R] = Key::Control;
  functionKeys_[0xff & XK_Caps_Lock] = Key::CapsLock;
  functionKeys_[0xff & XK_Shift_Lock] = Key::CapsLock;
  functionKeys_[0xff & XK_Meta_L] = Key::Meta;
  functionKeys_[0xff & XK_Meta_R] = Key::Meta;
  functionKeys_[0xff & XK_Alt_L] = Key::Alt;
  functionKeys_[0xff & XK_Alt_R] = Key::Alt;
  return true;
}
